﻿//Copyright (c) 2010 Christopher E. S. King (http://chriskingconsulting.com)
//
//Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
// (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do 
// so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Collections.Concurrent;
using System.Threading;
using System.Text;
using System.Diagnostics.Contracts;
using System.Collections;
using System.Security.Cryptography.X509Certificates;

using Microsoft.Web.Administration;

using King.Text;
using King.Extensions;
using King.Extensions.Collections;

namespace King.Automation.Web {

    [TextNodeHasSideEffect, TextNodeContainsContent, TextNodeContainsState]
    public abstract class IisManagerObject : TextNode {

        private bool m_owner;
        private ServerManager m_manager;
        private string m_server;

        public IisManagerObject() {
        }
        public IisManagerObject(string server) {
            m_server = server;
        }

        protected ServerManager Manager {
            get { return m_manager; }
        }

        public string Server {
            get { return m_server; }
        }

        protected override void Prolog(TextNodeContext context) {

            // try inherit a ServerManager
            var parent = context.Ancestors<IisManagerObject>().FirstOrDefault();
            if (parent != null) {
                if (m_server != null && m_server.CaseInsensitveEquals(parent.Server))
                    throw new ArgumentException("Cannot nest server '" + Server + "' in '" + parent.Server + "'.");

                m_server = parent.Server;
                m_manager = parent.Manager;

            // create a ServerManager
            } else {
                if (m_server == null)
                    m_server = Environment.GetEnvironmentVariable("COMPUTERNAME");
                m_manager = ServerManager.OpenRemote(Server);
                m_owner = true;
            }
        }
        protected override void Epilog(TextNodeContext context) {
            if (m_owner)
                m_manager.CommitChanges();
        }
    }

    public class IisServer : IisManagerObject, IEnumerable<object> {

        private List<object> m_content;

        public IisServer(string server)
            : base(server) {
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            return Content();
        }
        protected IEnumerable<object> Content() {
            if (m_content == null)
                m_content = new List<object>();
            return m_content;
        }

        public void Add(object element) {
            if (m_content == null)
                m_content = new List<object>();
            m_content.Add(element);
        }
        IEnumerator IEnumerable.GetEnumerator() {
            throw new NotSupportedException();
        }
        IEnumerator<object> IEnumerable<object>.GetEnumerator() {
            throw new NotImplementedException();
        }
    }

    public class IisApplicationPool : IisManagerObject {

        private ApplicationPool m_applicationPool;
        private string m_name;
        private bool m_processOrphaning;
        private bool m_disablePing;
        private bool m_disableRapidFailProtection;
        private int? m_idleTimeOut;

        public IisApplicationPool(string name,
            bool processOrphaning,
            bool disablePing,
            bool disableRaidFailProtection,
            int? idleTimeOut = null) {
            
            m_name = name;
            m_processOrphaning = processOrphaning;
            m_disablePing = disablePing;
            m_disableRapidFailProtection = disableRaidFailProtection;
            m_idleTimeOut = idleTimeOut;
        }

        private IEnumerable<object> ConfigureApplicationDomain(TextNodeContext context) {
            yield return "ManagedRuntimeVersion = v4.0";
            m_applicationPool.ManagedRuntimeVersion = "v4.0";

            if (m_disablePing == true) {
                yield return "ProcessModel.PingEnabled = false";
                m_applicationPool.ProcessModel.PingingEnabled = !m_disablePing;
            }

            if (m_idleTimeOut != null) {
                yield return "ProcessModel.IdleTimeout = " + m_idleTimeOut + " minutes";
                m_applicationPool.ProcessModel.IdleTimeout = TimeSpan.FromMinutes((int)m_idleTimeOut);
            }

            if (m_processOrphaning == true) {
                yield return "ProcessOrphaning.Enabled = true";
                m_applicationPool.Failure.OrphanWorkerProcess = true;
            }

            if (m_disableRapidFailProtection == true) {
                yield return "RapidFailProtection.Enabled = false";
                m_applicationPool.Failure.RapidFailProtection = false;
            }
        }
        private IEnumerable<object> Configure(TextNodeContext context) {
            var applicationPool = Manager.ApplicationPools.SingleOrDefault(
                o => o.Name.ToLower() == m_name.ToLower());

            if (applicationPool != null)
                Manager.ApplicationPools.Remove(applicationPool);

            yield return "Creating applicationPool '" + m_name + "' on '" + Server + "'";
            m_applicationPool = Manager.ApplicationPools.Add(m_name);

            yield return new TextIndent() {
                new TextSingleSpaced() {
                    ConfigureApplicationDomain(context)
                }
            };

            yield return Children(context);
        }

        public string Name {
            get { return m_name; }
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextSingleSpaced() {
                Configure(context),
            };
        }
    }

    public class IisSite : IisManagerObject {

        private string m_name;
        private string m_applicationPool { get; set; }
        private object m_binding { get; set; }
        private object m_wwwRoot { get; set; }

        public IisSite(string name) {
            m_name = name;
            m_wwwRoot = @"c:\inetpub\wwwroot\";
        }

        private IEnumerable<object> Configure(TextNodeContext context) {
            var site = Manager.Sites.SingleOrDefault(
                o => o.Name.ToLower() == m_name.ToLower());

            if (site != null)
                Manager.Sites.Remove(site);

            yield return "Creating site '" + m_name + "' on '" + Server + "'";
            site = Manager.Sites.Add(m_name, m_wwwRoot + m_name, 0);
            site.Bindings.Clear();

            if (m_applicationPool == null) {
                var applicationPool = context.Ancestors<IisApplicationPool>().FirstOrDefault();
                if (applicationPool != null)
                    m_applicationPool = applicationPool.Name;
            }

            if (m_applicationPool != null) {
                yield return "ApplicationPool = '" + m_applicationPool + "'";
                site.ApplicationDefaults.ApplicationPoolName = m_applicationPool;
            }

            yield return m_binding;
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextSingleSpaced() {
                Configure(context)
            };
        }

        public string Name {
            get { return m_name; }
        }
    }

    public class IisBinding : IisManagerObject {

        private string m_site { get; set; }
        private int m_port { get; set; }
        private bool m_https { get; set; }
        private byte[] m_certificateHash { get; set; }
        private string m_certificateStoreName { get; set; }
        private string m_ipAddress { get; set; }

        public IisBinding(string site,
            string ipAddress,
            int port = 80,
            bool https = false,
            string certificateStoreName = null,
            byte[] certificateHash = null) {

            m_site = site;
            m_ipAddress = ipAddress;
            m_port = port;
            m_https = https;
            m_certificateStoreName = certificateStoreName;
            m_certificateHash = certificateHash;
        }

        private IEnumerable<object> ConfigureBinding(TextNodeContext context) {
            if (m_site == null) {
                var site = context.Ancestors<IisSite>().FirstOrDefault();
                if (site != null)
                    m_site = site.Name;
            }

            var serverSite = Manager.Sites.SingleOrDefault(o => o.Name.ToLower() == m_site);
            if (serverSite == null)
                throw new ArgumentException("Site '" + serverSite + "' not found on server '" + Server + "'.");

            var bindingInformation = "{0}:{1}:".Substitute(m_ipAddress, m_port);
            var protocol = m_https ? "https" : "http";

            yield return "Adding binding '" + m_ipAddress + ":" + m_port.ToString() +
                " (" + protocol + ")' on '" + Server + "'";

            if (m_https == true) {
                var hash = m_certificateHash.Select(o => o.ToString("X")).StringJoin(" ");
                yield return new TextIndent() {
                        new TextSingleSpaced() {
                            "CertificateStoreName = '" + m_certificateStoreName + "'",
                            "CertificateHash = '" + hash + "'"
                        }
                    };
                serverSite.Bindings.Add(bindingInformation, m_certificateHash, m_certificateStoreName);

            } else {
                serverSite.Bindings.Add(bindingInformation, "http");
            }
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            return new TextSingleSpaced() {
                ConfigureBinding(context)
            };
        }
    }

    public class IisSslCertificate : TextNode {

        private string m_pfxFile;
        private string m_password;

        public IisSslCertificate(string pfxFile, string password) {
            m_pfxFile = pfxFile;
            m_password = password;
        }

        private IEnumerable<object> ConfigureCertificate(TextNodeContext context, X509Certificate2 certificate) {

            yield return "Friendly name is '" + certificate.FriendlyName + "'";
            yield return AddCertificate(certificate, StoreName.My, StoreLocation.LocalMachine);
            yield return AddCertificate(certificate, StoreName.Root, StoreLocation.LocalMachine);
        }
        private IEnumerable<object> AddCertificate(
            X509Certificate2 certificate, StoreName storeName, StoreLocation storeLocation) {

            yield return "Adding certificate to store '" + storeLocation + "/" + storeName + "'";
            var store = new X509Store(storeName, storeLocation);
            store.Open(OpenFlags.ReadWrite);
            store.Add(certificate);
            store.Close();
        }

        protected override IEnumerable<object> Children(TextNodeContext context) {
            yield return new[] { "Installing certificate '" + m_pfxFile + "'" + Environment.NewLine };

            var certificate = new X509Certificate2(m_pfxFile, m_password,
                X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);

            yield return new TextIndent() {
                new TextSingleSpaced() {
                    ConfigureCertificate(context, certificate)
                }
            };
        }
    }
}
